const express = require('express')
const { createBundleRenderer } = require('vue-server-renderer')
const fs = require('fs')
const favicon = require('serve-favicon')
const LRU = require('lru-cache')
const compression = require('compression')

const config = require('./config')

const DEFAULT_OPTIONS = {
  prodOnly: false,
}

module.exports = (app, options) => {
  options = Object.assign({}, DEFAULT_OPTIONS, options)

  const isProd = process.env.NODE_ENV === 'production'

  if (options.prodOnly && !isProd) return

  const templatePath = config.templatePath
  const { publicPath } = config.service.projectOptions

  try {
    // Vue bundle renderer
    let renderer
    // In development: wait for webpack compilation
    // when receiving a SSR request
    let readyPromise

    const directives = config.directives

    const lruCacheOptions = config.lruCacheOptions || {}

    const defaultRendererOptions = {
      cache: new LRU({
        max: 1000,
        maxAge: 1000 * 60 * 15,
        ...lruCacheOptions,
      }),
      runInNewContext: false,
      inject: false,
      directives,
      shouldPrefetch: (file, type) => {
        if (config.shouldNotPrefetch.indexOf(file) > -1) return false
        if (type === 'script' || type === 'style') return true
      },
      shouldPreload: (file, type) => {
        if (config.shouldNotPreload.indexOf(file) > -1) return false
        if (type === 'script' || type === 'style') return true
      },
    }

    if (isProd) {
      // In production: create server renderer using template and built server bundle.
      // The server bundle is generated by vue-ssr-webpack-plugin.
      const template = fs.readFileSync(templatePath, 'utf-8')
      const serverBundle = require(`${config.distPath}/vue-ssr-server-bundle.json`)
      // The client manifests are optional, but it allows the renderer
      // to automatically infer preload/prefetch links and directly add <script>
      // tags for any async chunks used during render, avoiding waterfall requests.
      const clientManifest = require(`${config.distPath}/vue-ssr-client-manifest.json`)
      renderer = createBundleRenderer(serverBundle, {
        ...defaultRendererOptions,
        template,
        clientManifest,
      })
    } else {
      // In development: setup the dev server with watch and hot-reload,
      // and create a new renderer on bundle / index template update.
      const { setupDevServer } = require('./dev-server')
      readyPromise = setupDevServer({
        server: app,
        templatePath,
        onUpdate: ({ serverBundle }, options) => {
          // Re-create the bundle renderer
          renderer = createBundleRenderer(serverBundle, {
            ...defaultRendererOptions,
            ...options,
          })
        },
      })
    }

    if (config.applyDefaultServer) {
      // Serve static files
      const serve = (filePath, cache) => express.static(filePath, {
        maxAge: cache && isProd ? config.staticCacheTtl : 0,
        index: false,
      })

      // Serve static files
      app.use(compression({ threshold: 0 }))
      app.use(favicon(config.favicon))
      if (config.api.hasPlugin('pwa')) {
        app.use('/service-worker.js', serve(config.serviceWorkerPath))
      }
      const serveStaticFiles = serve(config.distPath, true)
      app.use(publicPath, (req, res, next) => {
        if (/index\.html/g.test(req.path)) {
          next()
        } else {
          serveStaticFiles(req, res, next)
        }
      })
    }

    if (config.extendServer) {
      config.extendServer(app)
    }

    // Render the Vue app using the bundle renderer
    const renderApp = (req, res) => {
      res.setHeader('Content-Type', 'text/html')

      const context = Object.assign({
        req,
        url: req.url,
        title: config.defaultTitle,
        httpCode: 200,
      }, config.extendContext && config.extendContext(req, res, process))

      renderer.renderToString(context, (err, renderedHtml) => {
        let html = renderedHtml

        if (err || context.httpCode === 500) {
          console.error(`error during render url : ${req.url}`)

          // Render Error Page
          let errorHtml = config.error500Html
            ? fs.readFileSync(config.error500Html, 'utf-8')
            : '500 | Internal Server Error'

          if (err) {
            console.error(err)

            if (!isProd) {
              const errorMessage = `<pre>${err.stack}</pre>`
              config.error500Html
                ? errorHtml = errorHtml.replace('<!--server-error-msg-->', errorMessage)
                : errorHtml += errorMessage
            }

            if (config.onError) {
              config.onError(err)
            }
          }

          html = errorHtml

          res.status(500)
        } else {
          res.status(context.httpCode)
        }

        if (config.onRender) {
          config.onRender(res, context)
        }

        res.send(html)
      })
    }

    // Process SSR requests
    let ssr
    if (isProd) {
      ssr = renderApp
    } else {
      // In development: wait for webpack compilation
      // when receiving a SSR request
      ssr = (req, res) => {
        readyPromise.then(() => renderApp(req, res)).catch(console.error)
      }
    }
    app.get(`${publicPath}*`, (req, res, next) => {
      if (config.skipRequests(req)) {
        return next()
      }
      ssr(req, res)
    })
    return readyPromise
  } catch (e) {
    console.error(e)
  }
}
